// Copyright 2012 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.api.ads.common.lib.factory;

import com.google.api.ads.common.lib.conf.ConfigurationHelper;
import com.google.api.ads.common.lib.conf.ConfigurationLoadException;
import com.google.api.ads.common.lib.exception.ValidationException;
import com.google.api.ads.common.lib.soap.ProductFrameworkModule;
import com.google.api.ads.common.lib.utils.logging.AdsServiceLoggers;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Throwables;
import com.google.common.collect.Lists;

import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.ConfigurationException;

import java.io.File;
import java.net.URL;

import javax.annotation.Nullable;

/**
 * Configuration for Ads*Services classes.
 *
 * @author Adam Rogal
 * @author Joseph DiLallo
 * @deprecated the SOAP-framework-agnostic *Services utilities, the only classes
 *             which used this class, are now all deprecated.
 */
@Deprecated
public class FactoryConfiguration {

  private ProductFrameworkModule adWordsSoapModule;
  private ProductFrameworkModule dfaSoapModule;
  private ProductFrameworkModule dfpSoapModule;

  /**
   * Private constructor.
   */
  private FactoryConfiguration() {
  }

  /**
   * Gets the SOAP module to use for AdWords.
   */
  public ProductFrameworkModule getAdWordsSoapModule() {
    return adWordsSoapModule;
  }

  /**
   * Gets the SOAP module to use for DFA.
   */
  public ProductFrameworkModule getDfaSoapModule() {
    return dfaSoapModule;
  }

  /**
   * Gets the SOAP module to use for DFP.
   */
  public ProductFrameworkModule getDfpSoapModule() {
    return dfpSoapModule;
  }

  /**
   * Builder for FactoryConfiguration.
   */
  public static class Builder implements
      com.google.api.ads.common.lib.utils.Builder<FactoryConfiguration> {

    private FactoryConfiguration factoryConfiguration;
    private String adWordsSoapModuleClassString;
    private String dfaSoapModuleClassString;
    private String dfpSoapModuleClassString;
    @VisibleForTesting
    ConfigurationHelper configHelper;

    /**
     * Constructor.
     */
    public Builder() {
      this.factoryConfiguration = new FactoryConfiguration();
      this.configHelper = new ConfigurationHelper();
    }

    @VisibleForTesting
    Builder(ConfigurationHelper configHelper) {
      this.factoryConfiguration = new FactoryConfiguration();
      this.configHelper = configHelper;
    }

    public Builder fromFile() throws ConfigurationLoadException {
      return fromFile(Builder.DEFAULT_CONFIGURATION_FILENAME);
    }

    public Builder fromFile(String path) throws ConfigurationLoadException {
      return from(configHelper.fromFile(path));
    }

    public Builder fromFile(File path) throws ConfigurationLoadException {
      return from(configHelper.fromFile(path));
    }

    public Builder fromFile(URL path) throws ConfigurationLoadException {
      return from(configHelper.fromFile(path));
    }

    /**
     * Reads properties from the provided {@link Configuration} object.<br><br>
     * Known properties:
     * <ul>
     * <li>api.ads.soapClientHandlerModule</li>
     * </ul>
     *
     * @param config the factory configuration
     * @return Builder populated from the Configuration
     */
    public Builder from(Configuration config) {
      String adWordsSoapModuleClassString = config.getString("api.adwords.soapModule", null);
      if (adWordsSoapModuleClassString != null) {
        this.adWordsSoapModuleClassString = adWordsSoapModuleClassString;
      }
      String dfaSoapModuleClassString = config.getString("api.dfa.soapModule", null);
      if (dfaSoapModuleClassString != null) {
        this.dfaSoapModuleClassString = dfaSoapModuleClassString;
      }
      String dfpSoapModuleClassString = config.getString("api.dfp.soapModule", null);
      if (dfpSoapModuleClassString != null) {
        this.dfpSoapModuleClassString = dfpSoapModuleClassString;
      }
      return this;
    }

    /**
     * Specifies which SOAP client handler module class to use for AdWords.
     */
    public Builder withAdWordsSoapModule(ProductFrameworkModule soapModuleClass) {
      factoryConfiguration.adWordsSoapModule = soapModuleClass;
      return this;
    }

    /**
     * Specifies which SOAP client handler module class to use for DFA.
     */
    public Builder withDfaSoapModule(ProductFrameworkModule soapModuleClass) {
      factoryConfiguration.dfaSoapModule = soapModuleClass;
      return this;
    }

    /**
     * Specifies which SOAP client handler module class to use for DFP.
     */
    public Builder withDfpSoapModule(ProductFrameworkModule soapModuleClass) {
      factoryConfiguration.dfpSoapModule = soapModuleClass;
      return this;
    }

    /**
     * Tries to construct a FactoryConfiguration from file.
     *
     * @return Built FactoryConfiguration, or null if properties file doesn't exist.
     * @throws IllegalStateException if we encounter an unexpected ConfigurationLoadException or we
     *         fail validation.
     */
    @Nullable
    public FactoryConfiguration buildDefault() {
      FactoryConfiguration factoryConfiguration = null;
      try {
        factoryConfiguration = new FactoryConfiguration.Builder().fromFile().build();
      } catch (ConfigurationLoadException e) {
        if (Throwables.getRootCause(e) instanceof ConfigurationException) {
          // The default properties file does not exist. Silently ignore.
        } else {
          throw new IllegalStateException(e);
        }
      } catch (ValidationException e) {
        throw new IllegalStateException(
            "A SOAP module specified in your properties file is invalid.", e);
      }
      return factoryConfiguration;
    }

    /**
     * Builds the {@code FactoryConfiguration}.
     *
     * @return the built {@code FactoryConfiguration}
     * @throws ValidationException if the {@code FactoryConfiguration} did not
     *         validate
     */
    public FactoryConfiguration build() throws ValidationException {
      try {
        from(configHelper.createCombinedConfiguration(
            ConfigurationHelper.newList(Lists.newArrayList("ads.properties"), true), null));
      } catch (ConfigurationLoadException e) {
        // Should never occur.
        AdsServiceLoggers.ADS_API_LIB_LOG.debug("Factory configuration could not be loaded.");
      }
      validate(factoryConfiguration);
      return factoryConfiguration;
    }

    /**
     * Attempts to take a fully-qualified class name of a class extending
     * {@link ProductFrameworkModule} and instantiate an object of said class.
     *
     * @param className the name of the class to create an object of
     * @return an object of the given class
     * @throws InstantiationException if an object could not be instantiated
     * @throws IllegalAccessException if the class named could not be accessed
     * @throws ClassNotFoundException if the class named could not be found
     */
    private ProductFrameworkModule classNameToObject(String className)
        throws InstantiationException, IllegalAccessException, ClassNotFoundException {
      return (ProductFrameworkModule) Class.forName(className).newInstance();
    }

    /**
     * Validates the factory configuration.
     */
    private void validate(FactoryConfiguration factoryConfiguration) throws ValidationException {
      if (adWordsSoapModuleClassString != null) {
        try {
          factoryConfiguration.adWordsSoapModule = classNameToObject(adWordsSoapModuleClassString);
        } catch (ClassNotFoundException e) {
          throw new ValidationException("Cannot find SOAP module class: "
              + adWordsSoapModuleClassString, "api.adwords.soapModule", e);
        } catch (InstantiationException e) {
          throw new ValidationException("Cannot instantiate an object of class: "
              + adWordsSoapModuleClassString, "api.adwords.soapModule", e);
        } catch (IllegalAccessException e) {
          throw new ValidationException("Cannot access class: "
              + adWordsSoapModuleClassString, "api.adwords.soapModule", e);
        } catch (ClassCastException e) {
          throw new ValidationException("Given class is not a valid SOAP module class: "
              + adWordsSoapModuleClassString, "api.adwords.soapModule", e);
        }
      }
      if (dfaSoapModuleClassString != null) {
        try {
          factoryConfiguration.dfaSoapModule = classNameToObject(dfaSoapModuleClassString);
        } catch (ClassNotFoundException e) {
          throw new ValidationException("Cannot find SOAP module class: "
              + dfaSoapModuleClassString, "api.dfa.soapModule", e);
        } catch (InstantiationException e) {
          throw new ValidationException("Cannot instantiate an object of class: "
              + dfaSoapModuleClassString, "api.dfa.soapModule", e);
        } catch (IllegalAccessException e) {
          throw new ValidationException("Cannot access class: "
              + dfaSoapModuleClassString, "api.dfa.soapModule", e);
        } catch (ClassCastException e) {
          throw new ValidationException("Given class is not a valid SOAP module class: "
              + dfaSoapModuleClassString, "api.dfa.soapModule", e);
        }
      }
      if (dfpSoapModuleClassString != null) {
        try {
          factoryConfiguration.dfpSoapModule = classNameToObject(dfpSoapModuleClassString);
        } catch (ClassNotFoundException e) {
          throw new ValidationException("Cannot find SOAP module class: "
              + dfpSoapModuleClassString, "api.dfa.soapModule", e);
        } catch (InstantiationException e) {
          throw new ValidationException("Cannot instantiate an object of class: "
              + dfpSoapModuleClassString, "api.dfa.soapModule", e);
        } catch (IllegalAccessException e) {
          throw new ValidationException("Cannot access class: "
              + dfpSoapModuleClassString, "api.dfa.soapModule", e);
        } catch (ClassCastException e) {
          throw new ValidationException("Given class is not a valid SOAP module class: "
              + dfpSoapModuleClassString, "api.dfp.soapModule", e);
        }
      }
    }
  }
}
